﻿using NPOI.HSSF.UserModel;
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace DataFilter
{
    public class ExcelFilter
    {
        /// <summary>
        /// 创建一个Excel过滤器。
        /// </summary>
        /// <param name="duration">相同采集值的持续时间。</param>
        /// <param name="frequency">采样频率。</param>
        /// <param name="dValue">采样频率误差。</param>
        public ExcelFilter(int duration, int frequency, int dValue)
        {
            if (duration <= 0)
            {
                throw new ArgumentException($"{nameof(duration)}参数值必须大于0.");
            }

            if (frequency <= 0)
            {
                throw new ArgumentException($"{nameof(frequency)}参数值必须大于0.");
            }

            if (dValue <= 0)
            {
                throw new ArgumentException($"{nameof(dValue)}参数值必须大于0.");

            }
            _duration = duration;
            _frequency = frequency;
            _dValue = dValue;
        }

        private readonly int _duration;

        private readonly int _frequency;

        private readonly int _dValue;

        private IWorkbook _workBook = null!;

        private const int ValueColumnIndex = 6;

        private const int ArgumentCodeColumnIndex = 5;

        private const int DateTimeColumnIndex = 7;

        public event Action<string>? MissingArgumentCode;

        public Task FilterDataAsync(string inputFile, string outFile)
        {
            var fileInfo = new FileInfo(inputFile);

            return Task.Factory.StartNew(() =>
             {
                 using var fileStream = File.Open(fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);

                 try
                 {
                     if (string.Equals(fileInfo.Extension, ".xls", StringComparison.OrdinalIgnoreCase))
                     {
                         _workBook = new HSSFWorkbook(fileStream);
                     }
                     else
                     {
                         _workBook = new XSSFWorkbook(fileStream);
                     }
                 }
                 catch
                 {
                     _workBook = WorkbookFactory.Create(fileInfo.FullName);
                 }

                 FilterWorkbook();
                 using var outFileStream = File.Create(outFile);
                 _workBook.Write(outFileStream);
             });
        }

        private void FilterWorkbook()
        {
            var sheetCount = _workBook.NumberOfSheets;

            for (int sheetIndex = 0; sheetIndex < sheetCount; sheetIndex++)
            {
                var sheet = _workBook.GetSheetAt(sheetIndex);
                FilterWorksheet(sheet);
            }
        }

        private void FilterWorksheet(ISheet sheet)
        {
            var rowCount = sheet.LastRowNum;
            var rows = new List<IRow>();

            // 读取所有的行，创建一个数据词典。
            for (int rowIndex = 0; rowIndex < rowCount; rowIndex++)
            {
                var row = sheet.GetRow(rowIndex);
                rows.Add(row);
            }

            // 根据点位进行分组。
            // 例如：FM_Serov_6#R_CycleTime -> 6
            var getNumberRegex = new Regex("(?<=^FM_Serov_)[0-9]{1,3}(?=#.*)");
            var groupRowList = rows.GroupBy(row => getNumberRegex.Match(row.GetCell(ArgumentCodeColumnIndex).StringCellValue).Value);
            var groupList = groupRowList.ToList();
            groupList.RemoveAll(item => item.Key == string.Empty); // 移除不匹配的数据。

            var exceptions = new ConcurrentQueue<Exception>();

            // 指定 20 个线程处理数据。
            var reault = Parallel.ForEach(groupList, new ParallelOptions() { MaxDegreeOfParallelism = 20 }, groupRow =>
            {
                try
                {
                    CheckArgumentCodes(groupRow);
                    FilterPointPosition(groupRow);
                }
                catch (Exception e)
                {
                    exceptions.Enqueue(e);
                }
            });

            // Throw the exceptions here after the loop completes.
            if (!exceptions.IsEmpty)
            {
                throw new AggregateException(exceptions);
            }
        }

        /// <summary>
        /// 检查是否缺少必须的参数编码。
        /// </summary>
        /// <param name="groupRow">
        ///     根据点位进行分组后的数据组。
        /// </param>
        private void CheckArgumentCodes(IGrouping<string, IRow> groupRow)
        {
            // 匹配轴字符，例如：FM_Serov_000#Y_CycleTime => Y 。
            var regex = new Regex("(?<=^FM_Serov_[0-9]{1,3}#)[XYZR](?=_[a-zA-Z]*$)");

            var kkkk = "FM_Serov_000#R_CycleTime";
            var kkk = regex.Match(kkkk);

            // 对指定点位的轴进行分组。
            /*
                FM_Serov_1#X_CycleTime
                FM_Serov_1#X_TorqueMax
                FM_Serov_1#X_TorqueAvg
                FM_Serov_1#X_SpeedAvg
                FM_Serov_1#X_TorqueMaxTIme

                FM_Serov_1#X_TemperatrueMax
                FM_Serov_1#X_CurrentMax
                FM_Serov_1#X_FaultCode
                这些数据将构成一组数据，并且是根据 X 进行分组的。
             */
            var count = groupRow.Count();
            var groupRowList = groupRow.GroupBy(row => regex.Match(row.GetCell(ArgumentCodeColumnIndex).StringCellValue).Value);
            var axisGroupList = groupRowList.ToList();
            axisGroupList.RemoveAll(item => item.Key == string.Empty); // 移除不匹配的数据。

            foreach (var axisGroup in axisGroupList)
            {
                // 每一组 groupRow 都是相同点位的数据。
                CheckArgumentCodesByAxis(axisGroup, groupRow.Key);
            }
        }

        /// <summary>
        /// 检查  FM_Serov_1#X_*** “***”部分的数据。
        /// </summary>
        /// <param name="groupRow">根据点位和轴进行分组的数据组。</param>
        private void CheckArgumentCodesByAxis(IGrouping<string, IRow> groupRow, string pointPosition)
        {
            if (MissingArgumentCode is null)
            {
                return;
            }

            // 获取后缀字符串。
            // 例如：FM_Serov_000#Y_CycleTime => CycleTime。
            Regex cycleTimeRegex = new Regex("(?<=^FM_Serov_[0-9]{1,3}#[XYZR]_)CycleTime$");
            Regex torqueMaxRegex = new Regex("(?<=^FM_Serov_[0-9]{1,3}#[XYZR]_)TorqueMax$");
            Regex torqueAvgRegex = new Regex("(?<=^FM_Serov_[0-9]{1,3}#[XYZR]_)TorqueAvg$");
            Regex speedAvgRegex = new Regex("(?<=^FM_Serov_[0-9]{1,3}#[XYZR]_)SpeedAvg$");
            Regex torqueMaxTImeRegex = new Regex("(?<=^FM_Serov_[0-9]{1,3}#[XYZR]_)TorqueMaxTime$");

            var kk = cycleTimeRegex.Match(groupRow.First().GetCell(5).StringCellValue);

            var hasCycleTime = groupRow.Any(row =>
            {
                var cellValue = row.GetCell(ArgumentCodeColumnIndex).StringCellValue;
                return cycleTimeRegex.IsMatch(cellValue);
            });

            var hasTorqueMax = groupRow.Any(row =>
            {
                var cellValue = row.GetCell(ArgumentCodeColumnIndex).StringCellValue;
                return torqueMaxRegex.IsMatch(cellValue);
            });

            var hasTorqueAvgRegex = groupRow.Any(row =>
            {
                var cellValue = row.GetCell(ArgumentCodeColumnIndex).StringCellValue;
                return torqueAvgRegex.IsMatch(cellValue);
            });

            var hasSpeedAvgRegex = groupRow.Any(row =>
            {
                var cellValue = row.GetCell(ArgumentCodeColumnIndex).StringCellValue;
                return speedAvgRegex.IsMatch(cellValue);
            });

            var hasTorqueMaxTimeRegex = groupRow.Any(row =>
            {
                var cellValue = row.GetCell(ArgumentCodeColumnIndex).StringCellValue;
                return torqueMaxTImeRegex.IsMatch(cellValue);
            });

            // 进行线程安全处理。
            lock(MissingArgumentCode)
            {
                if (!hasCycleTime)
                {
                    MissingArgumentCode?.Invoke($"FM_Serov_{pointPosition}#{groupRow.Key}_CycleTime");
                }

                if (!hasTorqueMax)
                {
                    MissingArgumentCode?.Invoke($"FM_Serov_{pointPosition}#{groupRow.Key}_TorqueMax");
                }

                if (!hasTorqueAvgRegex)
                {
                    MissingArgumentCode?.Invoke($"FM_Serov_{pointPosition}#{groupRow.Key}_TorqueAvg");
                }

                if (!hasSpeedAvgRegex)
                {
                    MissingArgumentCode?.Invoke($"FM_Serov_{pointPosition}#{groupRow.Key}_SpeedAvg");
                }

                if (!hasTorqueMaxTimeRegex)
                {
                    MissingArgumentCode?.Invoke($"FM_Serov_{pointPosition}#{groupRow.Key}_TorqueMaxTime");
                }
            }
        }

        /// <summary>
        /// 过滤点位分组的数据组。
        /// </summary>
        /// <param name="groupRow">根据点位分组的数据组。</param>
        void FilterPointPosition(IGrouping<string, IRow> groupRow)
        {
            // 获取后缀字符串。
            // 例如：FM_Serov_000#Y_CycleTime => CycleTime。
            var regex = new Regex("(?<=FM_Serov_[0-9]{1,3}#[XYZR]_).[a-z|A-Z]*$");
            // 根据后缀英文字母进行分组。
            var suffixGroupList = groupRow.GroupBy(row => regex.Match(row.GetCell(ArgumentCodeColumnIndex).StringCellValue).Value);

            foreach (var suffixGroup in suffixGroupList)
            {
                // 对数据进行排序。
                // 例如：
                // 在第五列值为 “FM_Serov_9#X_TorqueMax”的行中，以同一行的第七列的日期时间进行排序。
                IOrderedEnumerable<IRow>? rows = suffixGroup.OrderBy(row => DateTime.Parse(row.GetCell(DateTimeColumnIndex).StringCellValue));
                var rowCount = rows.Count();

                // 如果小于两行，就没必要处理比较时间间隔了。
                if (rowCount < 2)
                {
                    continue;
                }

                SetFilterCellData(rows);
            }
        }

        private void SetFilterCellData(IOrderedEnumerable<IRow> rows)
        {
            // 至此 orderGroup 就是最终的需要处理的数据了。
            // 例如第五列所有值为 “FM_Serov_9#X_TorqueMax”的行，并且是经过进行时间排序后的行。
            var rowCount = rows.Count();
            string startValue = rows.First().GetCell(ValueColumnIndex).StringCellValue;
            var startDateTime = DateTime.Parse(rows.First().GetCell(DateTimeColumnIndex).StringCellValue);
            var frontDateTime = startDateTime; // 前一个日期时间。
            var presetDateTime = TimeSpan.FromMinutes(_duration); // 两个值变化的最大间隔时间。
            var freuenctDateTime = TimeSpan.FromMinutes(_frequency); // 采集频率间隔。
            var dValueDateTime = TimeSpan.FromMinutes(_dValue);

            for (int rowIndex = 1; rowIndex < rowCount; rowIndex++)
            {
                var currentRow = rows.ElementAt(rowIndex);
                var currentDateTime = DateTime.Parse(currentRow.GetCell(DateTimeColumnIndex).StringCellValue);
                var valueCell = currentRow.GetCell(ValueColumnIndex);

                // 如果时间偏差大于预设时间。
                if (currentDateTime - startDateTime > presetDateTime)
                {
                    var currentValue = valueCell.StringCellValue;

                    // 预设时间范围内如果采集值没有变更，那么就将超出预设时间范围外的单元格设为红色。
                    if (currentValue == startValue)
                    {
                        // 将单元格设置为红色。
                        valueCell.CellStyle = CreateRedColorCellStyle(valueCell.CellStyle);
                    }
                    else
                    {
                        // 直到遇到值变更的单元格，重置起始时间。
                        startDateTime = currentDateTime;
                        startValue = currentValue;
                    }
                }

                var errorDateTime = currentDateTime - frontDateTime;

                // 如果采样频率在误差范围外，那么单元格标记红色。
                if (!(errorDateTime >= freuenctDateTime - dValueDateTime
                    && errorDateTime <= freuenctDateTime + dValueDateTime))
                {
                    var dateTimeCell = currentRow.GetCell(DateTimeColumnIndex);
                    dateTimeCell.CellStyle = CreateBlueColorCellStyle(dateTimeCell.CellStyle);
                }

                frontDateTime = currentDateTime;
            }
        }

        private ICellStyle? _redStyle;

        private ICellStyle CreateRedColorCellStyle(ICellStyle baseStyle)
        {
            if (_redStyle is null)
            {
                _redStyle = _workBook.CreateCellStyle();
                _redStyle.CloneStyleFrom(baseStyle);
                _redStyle.FillForegroundColor = IndexedColors.Red.Index;
                _redStyle.FillPattern = FillPattern.SolidForeground;
            }

            return _redStyle;
        }

        private ICellStyle? _blueStyle;

        private ICellStyle CreateBlueColorCellStyle(ICellStyle baseStyle)
        {
            if (_blueStyle == null)
            {
                _blueStyle = _workBook.CreateCellStyle();
                _blueStyle.CloneStyleFrom(baseStyle);
                _blueStyle.FillForegroundColor = IndexedColors.Blue.Index;
                _blueStyle.FillPattern = FillPattern.SolidForeground;
            }

            return _blueStyle;
        }

        private ICellStyle? _greenStyle;

        private ICellStyle CreateGreenColorCellStyle(ICellStyle baseStyle)
        {
            if (_greenStyle is null)
            {
                _greenStyle = _workBook.CreateCellStyle();
                _greenStyle.CloneStyleFrom(baseStyle);
                _greenStyle.FillForegroundColor = IndexedColors.Green.Index;
                _greenStyle.FillPattern = FillPattern.SolidForeground;
            }

            return _greenStyle;
        }
    }
}
